"""
A channel implementing the Telnet protocol, see protocol.py for how the
protocol itself works, and constants.py for the various constants used in
Telnet.
"""

import socket
from expect import is_windows
from ..socket import SocketChannel
from .protocol import TelnetProtocol
from .constants import OPERATIONS, COMMAND, OPTION


class TelnetChannel(SocketChannel):
    """
    This channel implements a Telnet client that can be used with an
    expectable.
    
    To perform your own IAC processing, override the .iac_callback
    method.
    """
    
    def __init__(self, hostname, port=23, timeout=60, name=None):
        self.protocol = TelnetProtocol(self._iac_callback)
        super(TelnetChannel, self).__init__(socket=socket_, read_filter=self.protocol, name=name)
        
    @classmethod
    def _connect(cls, hostname, port, timeout):
        """
        Attempt to connect to the server until 'timeout' seconds have passed.
        """
        timer = Timer(timeout)
        while True:
            try:
                socket_ = cls._attempt_to_connect(hostname, port)
                return socket_
            except Exception, e:
                if is_windows:
                    raise Exception("need to define which exceptions to check")
                else:
                    if e.errno == errno.ECONNREFUSED:
                        pass
                    else:
                        raise
            if timer.remaining == 0:
                message = "Failed to connect to '{hostname}' on port '{port}'"
                raise FailedToConnectError(message.format(**locals()))
    
    @staticmethod
    def _attempt_to_connect(hostname, port):
        """
        Attempt to connect once to the server.
        """
        socket_ = socket.socket()
        socket_.connect((hostname, port))
        return socket_

    def _respond_yes(self, iac_sequence):
        # DO => WILL
        # DONT => WONT
        # WILL => DO
        # WONT => DONT
        if iac_sequence.command == COMMAND['DO']:
            command = COMMAND['WILL']
        elif iac_sequence.command == COMMAND['DONT']:
            command = COMMAND['WONT'] # only valid response
        elif iac_sequence.command == COMMAND['WILL']:
            command = COMMAND['DO']
        elif iac_sequence.command == COMMAND['WONT']:
            command = COMMAND['DONT'] # only valid response
        self.write_raw(IAC + command + iac_sequence.option)

    def _respond_no(self, iac_sequence):
        # DO => WONT
        # DONT => WONT
        # WILL => DONT
        # WONT => DONT
        if iac_sequence.command in (COMMAND['DO'], COMMAND['DONT']):
            command = COMMAND['WONT']
        elif iac_sequence.command in (COMMAND['WILL'], COMMAND['WONT']):
            command = COMMAND['DONT']
        else:
            message = "Invalid command in IAC sequence: {iac_sequence}"
            raise Exception(message.format(**locals()))
        self.write_raw(IAC + command + iac_sequence.option)
    
    def _iac_callback(self, iac_sequence):
        """
        Subclasses can override this method to perform their own IAC processing.
        """
        if iac_sequence.command in OPERATIONS:
            # DO/DONT/WILL/WONT
            # by default, respond in the negative to anything, except SGA and ECHO
            if iac_sequence.option in (OPTION['SGA'], OPTION['ECHO']):
                self._respond_yes(iac_sequence)
            else:
                self._respond_no(iac_sequence)
        elif iac_sequence.command == COMMAND['SB']:
            # ignore suboptions
            pass
        else:
            message = "Got malformed IAC sequence with command {0}"
            raise Exception(message.format(ord(iac_sequence.command)))
    